/*
 * winjump.c: support for Windows 7 jump lists.
 *
 * The Windows 7 jumplist is a customizable list defined by the
 * application. It is persistent across application restarts: the OS
 * maintains the list when the app is not running. The list is shown
 * when the user right-clicks on the taskbar button of a running app
 * or a pinned non-running application. We use the jumplist to
 * maintain a list of recently started saved sessions, started either
 * by doubleclicking on a saved session, or with the command line
 * "-load" parameter.
 *
 * Since the jumplist is write-only: it can only be replaced and the
 * current list cannot be read, we must maintain the contents of the
 * list persistantly in the registry. The file winstore.h contains
 * functions to directly manipulate these registry entries. This file
 * contains higher level functions to manipulate the jumplist.
 */

#include <assert.h>

#include "putty.h"
#include "storage.h"

#define MAX_JUMPLIST_ITEMS                                                     \
  30 /* PuTTY will never show more items in                                    \
      * the jumplist than this, regardless of                                  \
      * user preferences. */

/*
 * COM structures and functions.
 */
#ifndef PROPERTYKEY_DEFINED
#define PROPERTYKEY_DEFINED
typedef struct _tagpropertykey {
  GUID fmtid;
  DWORD pid;
} PROPERTYKEY;
#endif
#ifndef _REFPROPVARIANT_DEFINED
#define _REFPROPVARIANT_DEFINED
typedef PROPVARIANT *REFPROPVARIANT;
#endif
/* MinGW doesn't define this yet: */
#ifndef _PROPVARIANTINIT_DEFINED_
#define _PROPVARIANTINIT_DEFINED_
#define PropVariantInit(pvar) memset((pvar), 0, sizeof(PROPVARIANT))
#endif

#define IID_IShellLink IID_IShellLinkA

typedef struct ICustomDestinationListVtbl {
  HRESULT(__stdcall *QueryInterface)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppvObject);

  ULONG(__stdcall *AddRef)
  (
      /* [in] ICustomDestinationList*/ void *This);

  ULONG(__stdcall *Release)
  (
      /* [in] ICustomDestinationList*/ void *This);

  HRESULT(__stdcall *SetAppID)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [string][in] */ LPCWSTR pszAppID);

  HRESULT(__stdcall *BeginList)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [out] */ UINT *pcMinSlots,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppv);

  HRESULT(__stdcall *AppendCategory)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [string][in] */ LPCWSTR pszCategory,
      /* [in] IObjectArray*/ void *poa);

  HRESULT(__stdcall *AppendKnownCategory)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [in] KNOWNDESTCATEGORY*/ int category);

  HRESULT(__stdcall *AddUserTasks)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [in] IObjectArray*/ void *poa);

  HRESULT(__stdcall *CommitList)
  (
      /* [in] ICustomDestinationList*/ void *This);

  HRESULT(__stdcall *GetRemovedDestinations)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [in] */ const IID *const riid,
      /* [out] */ void **ppv);

  HRESULT(__stdcall *DeleteList)
  (
      /* [in] ICustomDestinationList*/ void *This,
      /* [string][unique][in] */ LPCWSTR pszAppID);

  HRESULT(__stdcall *AbortList)
  (
      /* [in] ICustomDestinationList*/ void *This);

} ICustomDestinationListVtbl;

typedef struct ICustomDestinationList {
  ICustomDestinationListVtbl *lpVtbl;
} ICustomDestinationList;

typedef struct IObjectArrayVtbl {
  HRESULT(__stdcall *QueryInterface)
  (
      /* [in] IObjectArray*/ void *This,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppvObject);

  ULONG(__stdcall *AddRef)
  (
      /* [in] IObjectArray*/ void *This);

  ULONG(__stdcall *Release)
  (
      /* [in] IObjectArray*/ void *This);

  HRESULT(__stdcall *GetCount)
  (
      /* [in] IObjectArray*/ void *This,
      /* [out] */ UINT *pcObjects);

  HRESULT(__stdcall *GetAt)
  (
      /* [in] IObjectArray*/ void *This,
      /* [in] */ UINT uiIndex,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppv);

} IObjectArrayVtbl;

typedef struct IObjectArray {
  IObjectArrayVtbl *lpVtbl;
} IObjectArray;

typedef struct IShellLinkVtbl {
  HRESULT(__stdcall *QueryInterface)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppvObject);

  ULONG(__stdcall *AddRef)
  (
      /* [in] IShellLink*/ void *This);

  ULONG(__stdcall *Release)
  (
      /* [in] IShellLink*/ void *This);

  HRESULT(__stdcall *GetPath)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][out] */ LPSTR pszFile,
      /* [in] */ int cch,
      /* [unique][out][in] */ WIN32_FIND_DATAA *pfd,
      /* [in] */ DWORD fFlags);

  HRESULT(__stdcall *GetIDList)
  (
      /* [in] IShellLink*/ void *This,
      /* [out] LPITEMIDLIST*/ void **ppidl);

  HRESULT(__stdcall *SetIDList)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] LPITEMIDLIST*/ void *pidl);

  HRESULT(__stdcall *GetDescription)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][out] */ LPSTR pszName,
      /* [in] */ int cch);

  HRESULT(__stdcall *SetDescription)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszName);

  HRESULT(__stdcall *GetWorkingDirectory)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][out] */ LPSTR pszDir,
      /* [in] */ int cch);

  HRESULT(__stdcall *SetWorkingDirectory)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszDir);

  HRESULT(__stdcall *GetArguments)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][out] */ LPSTR pszArgs,
      /* [in] */ int cch);

  HRESULT(__stdcall *SetArguments)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszArgs);

  HRESULT(__stdcall *GetHotkey)
  (
      /* [in] IShellLink*/ void *This,
      /* [out] */ WORD *pwHotkey);

  HRESULT(__stdcall *SetHotkey)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ WORD wHotkey);

  HRESULT(__stdcall *GetShowCmd)
  (
      /* [in] IShellLink*/ void *This,
      /* [out] */ int *piShowCmd);

  HRESULT(__stdcall *SetShowCmd)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ int iShowCmd);

  HRESULT(__stdcall *GetIconLocation)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][out] */ LPSTR pszIconPath,
      /* [in] */ int cch,
      /* [out] */ int *piIcon);

  HRESULT(__stdcall *SetIconLocation)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszIconPath,
      /* [in] */ int iIcon);

  HRESULT(__stdcall *SetRelativePath)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszPathRel,
      /* [in] */ DWORD dwReserved);

  HRESULT(__stdcall *Resolve)
  (
      /* [in] IShellLink*/ void *This,
      /* [unique][in] */ HWND hwnd,
      /* [in] */ DWORD fFlags);

  HRESULT(__stdcall *SetPath)
  (
      /* [in] IShellLink*/ void *This,
      /* [string][in] */ LPCSTR pszFile);

} IShellLinkVtbl;

typedef struct IShellLink {
  IShellLinkVtbl *lpVtbl;
} IShellLink;

typedef struct IObjectCollectionVtbl {
  HRESULT(__stdcall *QueryInterface)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ const GUID *const riid,
      /* [out] */ void **ppvObject);

  ULONG(__stdcall *AddRef)
  (
      /* [in] IShellLink*/ void *This);

  ULONG(__stdcall *Release)
  (
      /* [in] IShellLink*/ void *This);

  HRESULT(__stdcall *GetCount)
  (
      /* [in] IShellLink*/ void *This,
      /* [out] */ UINT *pcObjects);

  HRESULT(__stdcall *GetAt)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ UINT uiIndex,
      /* [in] */ const GUID *const riid,
      /* [iid_is][out] */ void **ppv);

  HRESULT(__stdcall *AddObject)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ void *punk);

  HRESULT(__stdcall *AddFromArray)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ IObjectArray *poaSource);

  HRESULT(__stdcall *RemoveObjectAt)
  (
      /* [in] IShellLink*/ void *This,
      /* [in] */ UINT uiIndex);

  HRESULT(__stdcall *Clear)
  (
      /* [in] IShellLink*/ void *This);

} IObjectCollectionVtbl;

typedef struct IObjectCollection {
  IObjectCollectionVtbl *lpVtbl;
} IObjectCollection;

typedef struct IPropertyStoreVtbl {
  HRESULT(__stdcall *QueryInterface)
  (
      /* [in] IPropertyStore*/ void *This,
      /* [in] */ const GUID *const riid,
      /* [iid_is][out] */ void **ppvObject);

  ULONG(__stdcall *AddRef)
  (
      /* [in] IPropertyStore*/ void *This);

  ULONG(__stdcall *Release)
  (
      /* [in] IPropertyStore*/ void *This);

  HRESULT(__stdcall *GetCount)
  (
      /* [in] IPropertyStore*/ void *This,
      /* [out] */ DWORD *cProps);

  HRESULT(__stdcall *GetAt)
  (
      /* [in] IPropertyStore*/ void *This,
      /* [in] */ DWORD iProp,
      /* [out] */ PROPERTYKEY *pkey);

  HRESULT(__stdcall *GetValue)
  (
      /* [in] IPropertyStore*/ void *This,
      /* [in] */ const PROPERTYKEY *const key,
      /* [out] */ PROPVARIANT *pv);

  HRESULT(__stdcall *SetValue)
  (
      /* [in] IPropertyStore*/ void *This,
      /* [in] */ const PROPERTYKEY *const key,
      /* [in] */ REFPROPVARIANT propvar);

  HRESULT(__stdcall *Commit)
  (
      /* [in] IPropertyStore*/ void *This);
} IPropertyStoreVtbl;

typedef struct IPropertyStore {
  IPropertyStoreVtbl *lpVtbl;
} IPropertyStore;

static const CLSID CLSID_DestinationList = {
    0x77f10cf0,
    0x3db5,
    0x4966,
    {0xb5, 0x20, 0xb7, 0xc5, 0x4f, 0xd3, 0x5e, 0xd6}};
static const CLSID CLSID_ShellLink = {
    0x00021401,
    0x0000,
    0x0000,
    {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
static const CLSID CLSID_EnumerableObjectCollection = {
    0x2d3468c1,
    0x36a7,
    0x43b6,
    {0xac, 0x24, 0xd3, 0xf0, 0x2f, 0xd9, 0x60, 0x7a}};
static const IID IID_IObjectCollection = {
    0x5632b1a4,
    0xe38a,
    0x400a,
    {0x92, 0x8a, 0xd4, 0xcd, 0x63, 0x23, 0x02, 0x95}};
static const IID IID_IShellLink = {
    0x000214ee,
    0x0000,
    0x0000,
    {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
static const IID IID_ICustomDestinationList = {
    0x6332debf,
    0x87b5,
    0x4670,
    {0x90, 0xc0, 0x5e, 0x57, 0xb4, 0x08, 0xa4, 0x9e}};
static const IID IID_IObjectArray = {
    0x92ca9dcd,
    0x5622,
    0x4bba,
    {0xa8, 0x05, 0x5e, 0x9f, 0x54, 0x1b, 0xd8, 0xc9}};
static const IID IID_IPropertyStore = {
    0x886d8eeb,
    0x8cf2,
    0x4446,
    {0x8d, 0x02, 0xcd, 0xba, 0x1d, 0xbd, 0xcf, 0x99}};
static const PROPERTYKEY PKEY_Title = {
    {0xf29f85e0,
     0x4ff9,
     0x1068,
     {0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9}},
    0x00000002};

/* Type-checking macro to provide arguments for CoCreateInstance()
 * etc, ensuring that 'obj' really is a 'type **'. */
#define typecheck(checkexpr, result) (sizeof(checkexpr) ? (result) : (result))
#define COMPTR(type, obj)                                                      \
  &IID_##type, typecheck((obj) - (type **)(obj), (void **)(void *)(obj))

static char putty_path[2048];

/*
 * Function to make an IShellLink describing a particular PuTTY
 * command. If 'appname' is null, the command run will be the one
 * returned by GetModuleFileName, i.e. our own executable; if it's
 * non-null then it will be assumed to be a filename in the same
 * directory as our own executable, and the return value will be NULL
 * if that file doesn't exist.
 *
 * If 'sessionname' is null then no command line will be passed to the
 * program. If it's non-null, the command line will be that text
 * prefixed with an @ (to load a PuTTY saved session).
 *
 * Hence, you can launch a saved session using make_shell_link(NULL,
 * sessionname), and launch another app using e.g.
 * make_shell_link("puttygen.exe", NULL).
 */
static IShellLink *make_shell_link(const char *appname, const char *sessionname)
{
  IShellLink *ret;
  char *app_path, *param_string, *desc_string;
  void *psettings_tmp;
  IPropertyStore *pPS;
  PROPVARIANT pv;

  /* Retrieve path to executable. */
  if (!putty_path[0])
    GetModuleFileName(NULL, putty_path, sizeof(putty_path) - 1);
  if (appname) {
    char *p, *q = putty_path;
    FILE *fp;

    if ((p = strrchr(q, '\\')) != NULL)
      q = p + 1;
    if ((p = strrchr(q, ':')) != NULL)
      q = p + 1;
    app_path = dupprintf("%.*s%s", (int)(q - putty_path), putty_path, appname);
    if ((fp = fopen(app_path, "r")) == NULL) {
      sfree(app_path);
      return NULL;
    }
    fclose(fp);
  } else {
    app_path = dupstr(putty_path);
  }

  /* Check if this is a valid session, otherwise don't add. */
  if (sessionname) {
    psettings_tmp = open_settings_r(sessionname);
    if (!psettings_tmp) {
      sfree(app_path);
      return NULL;
    }
    close_settings_r(psettings_tmp);
  }

  /* Create the new item. */
  if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  COMPTR(IShellLink, &ret)))) {
    sfree(app_path);
    return NULL;
  }

  /* Set path, parameters, icon and description. */
  ret->lpVtbl->SetPath(ret, app_path);

  if (sessionname) {
    /* The leading space is reported to work around a Windows 10
     * behaviour change in which an argument string starting with
     * '@' causes the SetArguments method to silently do the wrong
     * thing. */
    param_string = dupcat(" @", sessionname, NULL);
  } else {
    param_string = dupstr("");
  }
  ret->lpVtbl->SetArguments(ret, param_string);
  sfree(param_string);

  if (sessionname) {
    desc_string = dupcat("Connect to PuTTY session '", sessionname, "'", NULL);
  } else {
    assert(appname);
    desc_string = dupprintf("Run %.*s", (int)strcspn(appname, "."), appname);
  }
  ret->lpVtbl->SetDescription(ret, desc_string);
  sfree(desc_string);

  ret->lpVtbl->SetIconLocation(ret, app_path, 0);

  /* To set the link title, we require the property store of the link. */
  if (SUCCEEDED(
          ret->lpVtbl->QueryInterface(ret, COMPTR(IPropertyStore, &pPS)))) {
    PropVariantInit(&pv);
    pv.vt = VT_LPSTR;
    if (sessionname) {
      pv.pszVal = dupstr(sessionname);
    } else {
      assert(appname);
      pv.pszVal = dupprintf("Run %.*s", (int)strcspn(appname, "."), appname);
    }
    pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv);
    sfree(pv.pszVal);
    pPS->lpVtbl->Commit(pPS);
    pPS->lpVtbl->Release(pPS);
  }

  sfree(app_path);

  return ret;
}

/* Updates jumplist from registry. */
static void update_jumplist_from_registry(void)
{
  const char *piterator;
  UINT num_items;
  int jumplist_counter;
  UINT nremoved;

  /* Variables used by the cleanup code must be initialised to NULL,
   * so that we don't try to free or release them if they were never
   * set up. */
  ICustomDestinationList *pCDL = NULL;
  char *pjumplist_reg_entries = NULL;
  IObjectCollection *collection = NULL;
  IObjectArray *array = NULL;
  IShellLink *link = NULL;
  IObjectArray *pRemoved = NULL;
  int need_abort = FALSE;

  /*
   * Create an ICustomDestinationList: the top-level object which
   * deals with jump list management.
   */
  if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  COMPTR(ICustomDestinationList, &pCDL))))
    goto cleanup;

  /*
   * Call its BeginList method to start compiling a list. This gives
   * us back 'num_items' (a hint derived from systemwide
   * configuration about how many things to put on the list) and
   * 'pRemoved' (user configuration about things to leave off the
   * list).
   */
  if (!SUCCEEDED(pCDL->lpVtbl->BeginList(
          pCDL, &num_items, COMPTR(IObjectArray, &pRemoved))))
    goto cleanup;
  need_abort = TRUE;
  if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved)))
    nremoved = 0;

  /*
   * Create an object collection to form the 'Recent Sessions'
   * category on the jump list.
   */
  if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  COMPTR(IObjectCollection, &collection))))
    goto cleanup;

  /*
   * Go through the jump list entries from the registry and add each
   * one to the collection.
   */
  pjumplist_reg_entries = get_jumplist_registry_entries();
  piterator = pjumplist_reg_entries;
  jumplist_counter = 0;
  while (*piterator != '\0' &&
         (jumplist_counter < min(MAX_JUMPLIST_ITEMS, (int)num_items))) {
    link = make_shell_link(NULL, piterator);
    if (link) {
      UINT i;
      int found;

      /*
       * Check that the link isn't in the user-removed list.
       */
      for (i = 0, found = FALSE; i < nremoved && !found; i++) {
        IShellLink *rlink;
        if (SUCCEEDED(pRemoved->lpVtbl->GetAt(
                pRemoved, i, COMPTR(IShellLink, &rlink)))) {
          char desc1[2048], desc2[2048];
          if (SUCCEEDED(link->lpVtbl->GetDescription(
                  link, desc1, sizeof(desc1) - 1)) &&
              SUCCEEDED(rlink->lpVtbl->GetDescription(
                  rlink, desc2, sizeof(desc2) - 1)) &&
              !strcmp(desc1, desc2)) {
            found = TRUE;
          }
          rlink->lpVtbl->Release(rlink);
        }
      }

      if (!found) {
        collection->lpVtbl->AddObject(collection, link);
        jumplist_counter++;
      }

      link->lpVtbl->Release(link);
      link = NULL;
    }
    piterator += strlen(piterator) + 1;
  }
  sfree(pjumplist_reg_entries);
  pjumplist_reg_entries = NULL;

  /*
   * Get the array form of the collection we've just constructed,
   * and put it in the jump list.
   */
  if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
          collection, COMPTR(IObjectArray, &array))))
    goto cleanup;

  pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array);

  /*
   * Create an object collection to form the 'Tasks' category on the
   * jump list.
   */
  if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  COMPTR(IObjectCollection, &collection))))
    goto cleanup;

  /*
   * Add task entries for PuTTYgen and Pageant.
   */
  piterator = "Pageant.exe\0PuTTYgen.exe\0\0";
  while (*piterator != '\0') {
    link = make_shell_link(piterator, NULL);
    if (link) {
      collection->lpVtbl->AddObject(collection, link);
      link->lpVtbl->Release(link);
      link = NULL;
    }
    piterator += strlen(piterator) + 1;
  }

  /*
   * Get the array form of the collection we've just constructed,
   * and put it in the jump list.
   */
  if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
          collection, COMPTR(IObjectArray, &array))))
    goto cleanup;

  pCDL->lpVtbl->AddUserTasks(pCDL, array);

  /*
   * Now we can clean up the array and collection variables, so as
   * to be able to reuse them.
   */
  array->lpVtbl->Release(array);
  array = NULL;
  collection->lpVtbl->Release(collection);
  collection = NULL;

  /*
   * Create another object collection to form the user tasks
   * category.
   */
  if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  COMPTR(IObjectCollection, &collection))))
    goto cleanup;

  /*
   * Get the array form of the collection we've just constructed,
   * and put it in the jump list.
   */
  if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
          collection, COMPTR(IObjectArray, &array))))
    goto cleanup;

  pCDL->lpVtbl->AddUserTasks(pCDL, array);

  /*
   * Now we can clean up the array and collection variables, so as
   * to be able to reuse them.
   */
  array->lpVtbl->Release(array);
  array = NULL;
  collection->lpVtbl->Release(collection);
  collection = NULL;

  /*
   * Commit the jump list.
   */
  pCDL->lpVtbl->CommitList(pCDL);
  need_abort = FALSE;

  /*
   * Clean up.
   */
cleanup:
  if (pRemoved)
    pRemoved->lpVtbl->Release(pRemoved);
  if (pCDL && need_abort)
    pCDL->lpVtbl->AbortList(pCDL);
  if (pCDL)
    pCDL->lpVtbl->Release(pCDL);
  if (collection)
    collection->lpVtbl->Release(collection);
  if (array)
    array->lpVtbl->Release(array);
  if (link)
    link->lpVtbl->Release(link);
  sfree(pjumplist_reg_entries);
}

/* Clears the entire jumplist. */
void clear_jumplist(void)
{
  ICustomDestinationList *pCDL;

  if (CoCreateInstance(&CLSID_DestinationList,
                       NULL,
                       CLSCTX_INPROC_SERVER,
                       COMPTR(ICustomDestinationList, &pCDL)) == S_OK) {
    pCDL->lpVtbl->DeleteList(pCDL, NULL);
    pCDL->lpVtbl->Release(pCDL);
  }
}

/* Adds a saved session to the Windows 7 jumplist. */
void add_session_to_jumplist(const char *const sessionname)
{
  if ((osVersion.dwMajorVersion < 6) ||
      (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
    return; /* do nothing on pre-Win7 systems */

  if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
    update_jumplist_from_registry();
  } else {
    /* Make sure we don't leave the jumplist dangling. */
    clear_jumplist();
  }
}

/* Removes a saved session from the Windows jumplist. */
void remove_session_from_jumplist(const char *const sessionname)
{
  if ((osVersion.dwMajorVersion < 6) ||
      (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
    return; /* do nothing on pre-Win7 systems */

  if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
    update_jumplist_from_registry();
  } else {
    /* Make sure we don't leave the jumplist dangling. */
    clear_jumplist();
  }
}

/* Set Explicit App User Model Id to fix removable media error with
   jump lists */

BOOL set_explicit_app_user_model_id()
{
  DECL_WINDOWS_FUNCTION(
      static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR));

  static HMODULE shell32_module = 0;

  if (!shell32_module) {
    shell32_module = load_system32_dll("Shell32.dll");
    /*
     * We can't typecheck this function here, because it's defined
     * in <shobjidl.h>, which we're not including due to clashes
     * with all the manual-COM machinery above.
     */
    GET_WINDOWS_FUNCTION_NO_TYPECHECK(shell32_module,
                                      SetCurrentProcessExplicitAppUserModelID);
  }

  if (p_SetCurrentProcessExplicitAppUserModelID) {
    if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") ==
        S_OK) {
      return TRUE;
    }
    return FALSE;
  }
  /* Function doesn't exist, which is ok for Pre-7 systems */

  return TRUE;
}
